package com.sojson.util.security.springsecurity.service.impl;

import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

import com.sojson.base.impl.BaseUser;
import com.sojson.config.exception.TransErrorCode;
import com.sojson.constant.Constant;
import com.sojson.enums.core.EStatusLogin;
import com.sojson.project.sys.dept.service.DeptService;
import com.sojson.project.sys.role.service.RoleService;
import com.sojson.util.DateUtil;
import com.sojson.util.ExpUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.pwd.PwdUtil;
import com.sojson.util.token.Token;
import com.sojson.util.token.TokenDto;
import com.sojson.util.token.TokenUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * 用户验证处理
 *
 * @author liu
 * @date 2021-12-01
 */
@Slf4j
public class CustomDaoAuthenticationProviderImpl extends DaoAuthenticationProvider {

    @Lazy
    @Autowired
    private CustomDaoAuthenticationProviderImpl daoAuthenticationProvider;
    @Autowired
    private RoleService roleService;
    @Autowired
    private DeptService deptService;

    /**
     * 验证密码(如果是免密登录就直接跳过)
     *
     * @param userDetails       UserDetailsService.loadUserByUsername()返回的对象
     * @param authentication    AuthenticationManager.authenticate()的参数
     * @throws AuthenticationException
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        BaseUser user = ((Token)userDetails).getUser();
        try {
            // 判断登录的用户是否被禁用
            boolean submitLoginIsDisable = TokenUtil.submitLoginIsDisable(user);
            if (submitLoginIsDisable) {
                ExpUtil.throwEx(TransErrorCode.USER_FORBID_LOGIN);
            }

            // 判断是否为免密登录
            if (TokenUtil.pwdIsNo()) {
                return;
            }

            // 判断密码是否正确
            if (!PwdUtil.matchesPwd(authentication.getCredentials().toString(), user.getPwd())) {
                ExpUtil.throwEx(TransErrorCode.ERR_PWD);
            }
        } catch (Exception e) {
            // 记录用户登录日志
            TokenUtil.recordLogininfor(user.getUsername(), EStatusLogin.FAIL, e.getMessage());
            ExpUtil.throwEx(e);
        }
    }

    /**
     * 赋予权限
     */
    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
        UserDetails userDetails) {
        // 获取参数
        TokenDto param = TokenUtil.getLoginParam();

        // 获取用户
        Token token = (Token)userDetails;
        BaseUser user = token.getUser();

        // 创建并初始化Token
        Token createToken = null;
        try {
            createToken = daoAuthenticationProvider.createToken(user, param);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            // 记录用户登录日志
            TokenUtil.recordLogininfor(token.getUsername(), EStatusLogin.FAIL, e.getMessage());
            ExpUtil.throwEx(e);
        }

        // 刷新Token
        try {
            daoAuthenticationProvider.submitLoginUpdateToken(createToken);
        } catch (Exception e) {
            ExpUtil.throwEx(e);
        }

        // 保存到SpringSecurity
        Authentication createSuccessAuthentication =
            super.createSuccessAuthentication(principal, authentication, token);
        SecurityContextHolder.getContext().setAuthentication(createSuccessAuthentication);

        // 记录用户登录日志
        TokenUtil.recordLogininfor(user.getUsername(), EStatusLogin.SUCCESS, "登录成功");
        return createSuccessAuthentication;
    }

    /**
     * 创建并初始化Token
     *
     * @param user
     * @param param
     * @return
     * @throws Exception
     */
    public Token createToken(BaseUser user, Token param) throws Exception {
        Token token = null;
        // 删除旧Token
        Token lowToken = TokenUtil.getToken();
        if (StringUtil.isNotBlankObject(lowToken)) {
            TokenUtil.removeToken(lowToken.getId());
        }

        // 创建新Token
        token = TokenUtil.createToken();
        token.setUser(user);
        token.setLoginTime(DateUtil.nowLong());
        // 用户角色
        Set<String> roleIds = roleService.getRoleIdsByUserIdAndEnv(user.getId(), param.getLoginEnv());
        if (roleIds.size() < 1) {
            roleIds.add(Constant.DEFAULT_EMPTY);
        }
        token.setRoleIds(roleIds);
        // 用户可操作的角色
        Set<String> roleIdsExist = roleService.getRoleIdsByUserIdAndEnv(user.getId(), null);
        if (roleIdsExist.size() < 1) {
            roleIdsExist.add(Constant.DEFAULT_EMPTY);
        }
        token.setRoleIdsExist(roleIdsExist);
        Set<String> deptIds = deptService.listIds(user);
        if (deptIds.size() < 1) {
            deptIds.add(Constant.DEFAULT_EMPTY);
        }
        token.setDepts(deptIds);

        // 给用户赋初值
        daoAuthenticationProvider.userLoginInitGeneral(token, param);
        return token;
    }

    /**
     * 用户登录后给用户赋初值(普通登录方式)
     *
     * @param token 要赋值的对象
     * @param param 客户端传过来的参数
     * @throws Exception
     */
    public void userLoginInitGeneral(Token token, Token param) throws Exception {
        // 设置登录环境
        token.setLoginEnv(param.getLoginEnv());
        // 设置登录类型
        token.setLoginType(param.getLoginType());
        // 设置登录设备
        token.setTypeLoginDevice(param.getTypeLoginDevice());
        // 设置保存在线用户的key
        token.setKeyLoginDevice();

        daoAuthenticationProvider.userLoginInit(token.getUser(), param);
    }

    /**
     * 用户登录后给用户赋初值
     *
     * @param user  要赋值的对象
     * @param param 客户端传过来的参数
     * @throws Exception
     */
    public void userLoginInit(BaseUser user, Token param) throws Exception {
        // 设置保存在线用户的key
        // user.setCellphoneOrComputerKey();
    }

    /**
     * 登录后更新Token
     *
     * @param token
     * @throws Exception
     */
    public void submitLoginUpdateToken(Token token) throws Exception {
        // 更新Token
        TokenUtil.refreshToken(token);
    }

}